// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <cantor/type/value.h>

namespace cantor {
    turbo::Status Value::initialize(const cantor::pb::Value &value) {
        _value = value;
        return initialize_valid(_value);
    }

    turbo::Status Value::initialize(cantor::pb::Value &&value) {
        _value = std::move(value);
        return initialize_valid(_value);
    }

    turbo::Status Value::initialize_valid(const cantor::pb::Value &value) {
        if (value.has_is_null() && value.is_null()) {
            return turbo::OkStatus();
        }
        switch (value.type()) {
            case pb::TYPE_KIND_INT8: {
                if (!value.has_int8_value()) {
                    return turbo::data_loss_error("missing int8 value");
                }
                auto int8_value = value.int8_value();
                if (int8_value < std::numeric_limits<int8_t>::min() || int8_value > std::numeric_limits<
                        int8_t>::max()) {
                    return turbo::data_loss_error("value out of range");
                }
                break;
            }
            case pb::TYPE_KIND_INT16: {
                if (!value.has_int16_value()) {
                    return turbo::data_loss_error("missing int16 value");
                }
                auto int16_value = value.int16_value();
                if (int16_value < std::numeric_limits<int16_t>::min() || int16_value > std::numeric_limits<
                        int16_t>::max()) {
                    return turbo::data_loss_error("value out of range");
                }
                break;
            }
            case pb::TYPE_KIND_INT32: {
                if (!value.has_int32_value()) {
                    return turbo::data_loss_error("missing int32 value");
                }
                auto int32_value = value.int32_value();
                if (int32_value < std::numeric_limits<int32_t>::min() || int32_value > std::numeric_limits<
                        int32_t>::max()) {
                    return turbo::data_loss_error("value out of range");
                }
                break;
            }
            case pb::TYPE_KIND_INT64: {
                if (!value.has_int64_value()) {
                    return turbo::data_loss_error("missing int64 value");
                }
                break;
            }
            case pb::TYPE_KIND_INT128: {
                if (!value.has_int128_value()) {
                    return turbo::data_loss_error("missing int128 value");
                }
                break;
            }
            case pb::TYPE_KIND_UINT8: {
                if (!value.has_uint8_value()) {
                    return turbo::data_loss_error("missing uint8 value");
                }
                auto uint8_value = value.uint8_value();
                if (uint8_value > std::numeric_limits<uint8_t>::max()) {
                    return turbo::data_loss_error("value out of range");
                }
                break;
            }
            case pb::TYPE_KIND_UINT16: {
                if (!value.has_uint16_value()) {
                    return turbo::data_loss_error("missing uint16 value");
                }
                auto uint16_value = value.uint16_value();
                if (uint16_value > std::numeric_limits<uint16_t>::max()) {
                    return turbo::data_loss_error("value out of range");
                }
                break;
            }
            case pb::TYPE_KIND_UINT32: {
                if (!value.has_uint32_value()) {
                    return turbo::data_loss_error("missing uint32 value");
                }
                auto uint32_value = value.uint32_value();
                if (uint32_value > std::numeric_limits<uint32_t>::max()) {
                    return turbo::data_loss_error("value out of range");
                }
                break;
            }
            case pb::TYPE_KIND_UINT64: {
                if (!value.has_uint64_value()) {
                    return turbo::data_loss_error("missing uint64 value");
                }
                break;
            }
            case pb::TYPE_KIND_UINT128: {
                if (!value.has_uint128_value()) {
                    return turbo::data_loss_error("missing uint128 value");
                }
                break;
            }
            case pb::TYPE_KIND_FP8: {
                if (!value.has_fp8_value()) {
                    return turbo::data_loss_error("missing fp8 value");
                }
                break;
            }
            case pb::TYPE_KIND_FP16: {
                if (!value.has_fp16_value()) {
                    return turbo::data_loss_error("missing fp16 value");
                }
                break;
            }
            case pb::TYPE_KIND_FP32: {
                if (!value.has_fp32_value()) {
                    return turbo::data_loss_error("missing fp32 value");
                }
                break;
            }
            case pb::TYPE_KIND_FP64: {
                if (!value.has_fp64_value()) {
                    return turbo::data_loss_error("missing fp64 value");
                }
                break;
            }
            case pb::TYPE_KIND_STRING: {
                if (!value.has_string_value()) {
                    return turbo::data_loss_error("missing string value");
                }
                break;
            }
            case pb::TYPE_KIND_BINARY: {
                if (!value.has_binary_value()) {
                    return turbo::data_loss_error("missing binary value");
                }
                break;
            }
            case pb::TYPE_KIND_TIMESTAMP: {
                if (!value.has_timestamp_value()) {
                    return turbo::data_loss_error("missing timestamp value");
                }
                break;
            }
            case pb::TYPE_KIND_DURATION: {
                if (!value.has_duration_value()) {
                    return turbo::data_loss_error("missing duration value");
                }
                break;
            }
            case pb::TYPE_KIND_BOOLEAN: {
                if (!value.has_bool_val()) {
                    return turbo::data_loss_error("missing bool value");
                }
                break;
            }
            case pb::TYPE_KIND_DATETIME: {
                if (!value.has_datetime_value()) {
                    return turbo::data_loss_error("missing datetime value");
                }
                break;
            }
            case pb::TYPE_KIND_INTERVAL: {
                if (!value.has_interval_value()) {
                    return turbo::data_loss_error("missing interval value");
                }
                break;
            }
            case pb::TYPE_KIND_DECIMAL64: {
                if (!value.has_decimal64_value()) {
                    return turbo::data_loss_error("missing decimal64 value");
                }
                break;
            }
            case pb::TYPE_KIND_DECIMAL128: {
                if (!value.has_decimal128_value()) {
                    return turbo::data_loss_error("missing decimal128 value");
                }
                break;
            }
            case pb::TYPE_KIND_ENUM: {
                if (!value.has_enum_int_value() && !value.has_enum_str_value()) {
                    return turbo::data_loss_error("missing enum int/string value");
                }
                break;
            }
            case pb::TYPE_KIND_ARRAY: {
                if (!value.has_array_value()) {
                    return turbo::data_loss_error("missing array value");
                }
                auto &array = value.array_value();
                for (auto &item: array.data()) {
                    TURBO_RETURN_NOT_OK(initialize_valid(item));
                }
                break;
            }
            case pb::TYPE_KIND_MAP: {
                if (!value.has_map_value()) {
                    return turbo::data_loss_error("missing map value");
                }
                auto &map = value.map_value();
                if (map.data().empty()) {
                    break;
                }
                for (auto &item: map.data()) {
                    if (!item.has_key()) {
                        return turbo::data_loss_error("missing key value");
                    }
                    if (!item.has_value()) {
                        return turbo::data_loss_error("missing value for key");
                    }
                    TURBO_RETURN_NOT_OK(initialize_valid(item.key()));
                    TURBO_RETURN_NOT_OK(initialize_valid(item.value()));
                }
                break;
            }

            case pb::TYPE_KIND_SET: {
                if (!value.has_set_value()) {
                    return turbo::data_loss_error("missing set value");
                }
                auto &set = value.set_value();
                if (set.data().empty()) {
                    break;
                }
                for (auto &item: set.data()) {
                    TURBO_RETURN_NOT_OK(initialize_valid(item));
                }
                break;
            }
            case pb::TYPE_KIND_STRUCT: {
                if (!value.has_struct_value()) {
                    return turbo::data_loss_error("missing struct value");
                }
                auto &struct_value = value.struct_value();
                if (struct_value.names_size() != struct_value.values_size()) {
                    return turbo::data_loss_error("struct type name and values not match");
                }
                for (auto &item: struct_value.values()) {
                    TURBO_RETURN_NOT_OK(initialize_valid(item));
                }
                break;
            }
            case pb::TYPE_KIND_UNION: {
                if (!value.has_union_value()) {
                    return turbo::data_loss_error("missing union value");
                }
                auto &union_value = value.union_value();
                if (!union_value.has_name()) {
                    return turbo::data_loss_error("missing name value");
                }
                if (!union_value.has_value()) {
                    return turbo::data_loss_error("missing value for name");
                }
                TURBO_RETURN_NOT_OK(initialize_valid(union_value.value()));
                break;
            }
            case pb::TYPE_KIND_TUPLE: {
                if (!value.has_tuple_value()) {
                    return turbo::data_loss_error("missing tuple value");
                }
                auto &tuple_value = value.tuple_value();
                for (auto &item: tuple_value.values()) {
                    TURBO_RETURN_NOT_OK(initialize_valid(item));
                }
                break;
            }
            case pb::TYPE_KIND_JSON: {
                if (!value.has_json_value()) {
                    return turbo::data_loss_error("missing json value");
                }
                break;
            }
            case pb::TYPE_KIND_PROTO: {
                if (!value.has_proto_value()) {
                    return turbo::data_loss_error("missing proto value");
                }
                auto &proto_value = value.proto_value();
                if (!proto_value.has_uri()) {
                    return turbo::data_loss_error("missing uri");
                }
                break;
            }

            default:
                return turbo::data_loss_error("invalid value type");
        }
        return turbo::OkStatus();
    }

    turbo::Result<ValueView>  ValueView::create(const pb::Value  &value, bool sure_valid) {
        if (!sure_valid) {
            TURBO_RETURN_NOT_OK(Value::initialize_valid(value));
        }
        return ValueView(value);
    }

    bool ValueView::is_null() const {
        if (_value.has_is_null() && _value.is_null()) {
            return true;
        }
        return false;
    }

    pb::TypeKind ValueView::type() const {
        return _value.type();
    }

    turbo::Result<int8_t> ValueView::as_int8() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_INT8) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.int8_value();
    }

    turbo::Result<int8_t> ValueView::as_int16() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_INT16) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.int16_value();
    }

    turbo::Result<int8_t> ValueView::as_int32() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_INT32) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.int32_value();
    }

    turbo::Result<int8_t> ValueView::as_int64() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_INT64) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.int64_value();
    }

    turbo::Result<turbo::int128> ValueView::as_int128() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_INT128) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return turbo::make_int128(_value.int128_value().hi(), _value.int128_value().low());
    }

    turbo::Result<int8_t> ValueView::as_uint8() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_UINT8) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.uint8_value();
    }

    turbo::Result<int8_t> ValueView::as_uint16() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_UINT16) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.uint16_value();
    }

    turbo::Result<int8_t> ValueView::as_uint32() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_UINT32) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.uint32_value();
    }

    turbo::Result<int8_t> ValueView::as_uint64() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_UINT64) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.uint64_value();
    }

    turbo::Result<turbo::uint128> ValueView::as_uint128() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_UINT128) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return turbo::make_uint128(_value.uint128_value().hi(), _value.uint128_value().low());
    }

    turbo::Result<float> ValueView::as_fp32() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_FP32) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.fp32_value();
    }

    turbo::Result<double> ValueView::as_fp64() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_FP64) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.fp64_value();
    }

    turbo::Result<std::string_view> ValueView::as_string() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_STRING) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.string_value();
    }

    turbo::Result<turbo::span<const uint8_t> > ValueView::as_binary() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_BINARY) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto v = _value.binary_value();
        turbo::span<const uint8_t> span(reinterpret_cast<const uint8_t *>(v.data()), v.size());
        return span;
    }

    turbo::Result<turbo::Time> ValueView::as_timestamp() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_TIMESTAMP) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return turbo::Time::from_nanoseconds(_value.timestamp_value());
    }

    turbo::Result<turbo::Duration> ValueView::as_duration() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_DURATION) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return turbo::Duration::nanoseconds(_value.duration_value());
    }

    turbo::Result<bool> ValueView::as_bool() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_BOOLEAN) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.bool_val();
    }

    turbo::Result<turbo::CivilSecond> ValueView::as_datatime() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_DATETIME) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto t = turbo::Time::from_nanoseconds(_value.datetime_value());
        auto civil_second = turbo::Time::to_civil_second(t, turbo::TimeZone::utc());
        return civil_second;
    }

    turbo::Result<turbo::CivilSecond> ValueView::as_interval() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_INTERVAL) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto t = turbo::Time::from_nanoseconds(_value.interval_value());
        auto civil_second = turbo::Time::to_civil_second(t, turbo::TimeZone::utc());
        return civil_second;
    }

    turbo::Result<int32_t> ValueView::as_enum() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_ENUM) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.enum_int_value();
    }

    turbo::Result<turbo::span<const pb::Value> > ValueView::as_array() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_ARRAY) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto v = _value.array_value().data();
        turbo::span<const pb::Value> span(reinterpret_cast<const pb::Value*>(v.data()), v.size());
        return span;
    }

    turbo::Result<turbo::span<const pb::Value::MapPair> > ValueView::as_map() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_MAP) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto v = _value.map_value().data();
        turbo::span<const pb::Value::MapPair> span(reinterpret_cast<const pb::Value::MapPair*>(v.data()), v.size());
        return span;
    }

    turbo::Result<turbo::span<const pb::Value> > ValueView::as_set() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_SET) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto v = _value.set_value().data();
        turbo::span<const pb::Value> span(reinterpret_cast<const pb::Value*>(v.data()), v.size());
        return span;
    }

    turbo::Result<turbo::span<const std::string> > ValueView::as_struct_names() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_STRUCT) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto v = _value.struct_value().names();
        turbo::span<const std::string> span(reinterpret_cast<const std::string*>(v.data()), v.size());
        return span;
    }

    turbo::Result<turbo::span<const pb::Value> > ValueView::as_struct_values() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_STRUCT) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto v = _value.struct_value().values();
        turbo::span<const pb::Value> span(reinterpret_cast<const pb::Value*>(v.data()), v.size());
        return span;
    }

    turbo::Result<std::string_view> ValueView::as_union_name() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_UNION) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return _value.union_value().name();
    }

    turbo::Result<const pb::Value* > ValueView::as_union_value() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_UNION) {
            return turbo::invalid_argument_error("invalid value type");
        }
        return &_value.union_value().value();

    }

    turbo::Result<turbo::span<const pb::Value> > ValueView::as_tuple_values() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_TUPLE) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto v = _value.tuple_value().values();
        turbo::span<const pb::Value> span(reinterpret_cast<const pb::Value*>(v.data()), v.size());
        return span;
    }

    turbo::Result<std::string_view> ValueView::as_json() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_JSON) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto &v = _value.json_value();

        return v;
    }

    turbo::Result<std::string_view> ValueView::as_proto_uri() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_PROTO) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto &v = _value.proto_value().uri();

        return v;
    }

    turbo::Result<turbo::span<const uint8_t> > ValueView::as_proto_data() const {
        if (_value.has_is_null() && _value.is_null()) {
            return turbo::data_loss_error("missing null value");
        }
        if (_value.type() != pb::TYPE_KIND_PROTO) {
            return turbo::invalid_argument_error("invalid value type");
        }
        auto &v = _value.proto_value().data();

        return turbo::span<const uint8_t>(reinterpret_cast<const uint8_t*>(v.data()), v.size());
    }
} // namespace cantor
